﻿#region
//
// Author : Guangbo Zhang
// Email : guangboo49@gmail.com
//
#endregion

using System;
using System.Collections.Generic;
using System.Text;
using System.IO;

namespace Json {
    internal class JsonReader : IJsonReader, IDisposable {
        private BufferedCharReader _internalReader;

        public JsonReader(TextReader reader) {
            if (reader == null)
                throw new ArgumentNullException("reader");

            _internalReader = new BufferedCharReader(reader);
        }

        public JsonReader(string jsonString) {
            if (jsonString == null || jsonString.Length == 0)
                throw new ArgumentNullException("jsonString");

            _internalReader = new BufferedCharReader(new StringReader(jsonString));
        }

        public int Line {
            get { return _internalReader.LineNumber; }
        }

        public int Column {
            get { return _internalReader.LinePosition; }
        }

        #region ReadNumber

        /// <summary>
        /// Read a JsonInt, JsonInt64, JsonFloat or JsonDouble object.
        /// </summary>
        /// <returns></returns>
        public virtual Number ReadNumber() {
            CharBuffer buffer = new CharBuffer();
            char c = _internalReader.Next();

            if (char.IsNumber(c) || c == TerminalSymbol.NEGATIVE || c == TerminalSymbol.POSITIVE) {
                buffer.Push(c);
                bool hasE = false;
                bool hasDot = false;
                bool hasFlag = false;

                char last = BufferedCharReader.EOF;

                while (_internalReader.More()) {
                    c = _internalReader.Next();

                    if (char.IsNumber(c)) {
                        buffer.Push(c);
                    } else {
                        switch (c) {
                            case TerminalSymbol.e:
                            case TerminalSymbol.E:
                                if (!hasE) {
                                    buffer.Push(c);
                                    hasE = true;
                                } else {
                                    throw new SyntaxError("E alrady exists.");
                                }
                                break;
                            case TerminalSymbol.DOT:
                                if (!hasDot) {
                                    buffer.Push(c);
                                    hasDot = true;
                                } else {
                                    throw new SyntaxError(". already exists.");
                                }
                                break;
                            case TerminalSymbol.POSITIVE:
                            case TerminalSymbol.NEGATIVE:
                                if (last == TerminalSymbol.E || last == TerminalSymbol.e) {
                                    if (!hasFlag) {
                                        buffer.Push(c);
                                        hasFlag = true;
                                    } else {
                                        throw new SyntaxError(". already exists.");
                                    }
                                } else {
                                    throw new SyntaxError(". already exists.");
                                }
                                break;
                            //case TerminalSymbol.L:
                            //    if (!hasL) {
                            //        buffer.Push(c);
                            //        hasL = true;
                            //    } else {
                            //        throw new SyntaxError("L already exists.");
                            //    }
                            //    break;
                            //case TerminalSymbol.f:
                            //    if (!hasF) {
                            //        buffer.Push(c);
                            //        hasF = true;
                            //    } else {
                            //        throw new SyntaxError("f already exists.");
                            //    }
                            //    break;
                            //case TerminalSymbol.d:
                            //    if (!hasD) {
                            //        buffer.Push(c);
                            //        hasD = true;
                            //    } else {
                            //        throw new SyntaxError("d already exists.");
                            //    }
                            //    break;
                            case TerminalSymbol.SPACE:
                            case TerminalSymbol.ENTER:
                            case TerminalSymbol.NEWLINE:
                            case TerminalSymbol.TABLE:
                                break;
                            case TerminalSymbol.COMMA:
                            //case TerminalSymbol.RIGHT_PARENTHESIS:
                            case TerminalSymbol.RIGHT_BRACE:
                            case TerminalSymbol.RIGHT_SQUARE_BRACKET:
                                _internalReader.Back();
                                goto EXIT_LOOP;
                            default:
                                throw new SyntaxError(string.Format("Not supported charactor. {0}", c));
                        }
                    }

                    last = c;
                }

            EXIT_LOOP:
                //c = buffer.Pop();

                string str = buffer.GetString();

                return Number.Parse(str);
                #region old
                // Int64
                //if (c == TerminalSymbol.L) {
                //    long v;
                //    if (long.TryParse(str, out v)) {
                //        return new JsonInt64(v);
                //    } else {
                //        throw new SyntaxError();
                //    }
                //} else if (c == TerminalSymbol.f) { // Float
                //    float v;
                //    if (float.TryParse(str, out v)) {
                //        return new JsonFloat(v);
                //    } else {
                //        throw new SyntaxError();
                //    }
                //} else if (c == TerminalSymbol.d) {
                //    double v;
                //    if (double.TryParse(str, out v)) {
                //        return new JsonDouble(v);
                //    } else {
                //        throw new SyntaxError();
                //    }
                //} else if (char.IsNumber(c)) {
                //    buffer.Push(c);
                //    str = buffer.GetString();

                //    if (str.IndexOf(TerminalSymbol.DOT) > -1) { // Double
                //        double v;
                //        if (double.TryParse(str, out v)) {
                //            return new JsonDouble(v);
                //        } else {
                //            throw new SyntaxError();
                //        }
                //    } else { // Int32
                //        int v;
                //        if (int.TryParse(str, out v)) {
                //            return new JsonInt(v);
                //        } else {
                //            long vx;
                //            if (long.TryParse(str, out vx)) {
                //                return new JsonInt64(vx);
                //            } else {
                //                double dx;
                //                if (double.TryParse(str, out dx)) {
                //                    return new JsonDouble(dx);
                //                } else {
                //                    throw new SyntaxError("not a number", str);
                //                }
                //            }
                //        }
                //    }
                //} else {
                //    throw new SyntaxError();
                //}
                #endregion
            } else {
                throw new SyntaxError();
            }            
        }

        #endregion

        #region ReadBoolean

        /// <summary>
        /// Read a jsonboolean object
        /// </summary>
        /// <returns></returns>
        public virtual bool ReadBoolean() {
            CharBuffer buffer = new CharBuffer();
            char c;
            while ((c = _internalReader.Next()) != BufferedCharReader.EOF &&
                c != TerminalSymbol.COMMA &&
                //c != TerminalSymbol.LEFT_PARENTHESIS &&
                c != TerminalSymbol.RIGHT_SQUARE_BRACKET &&
                c != TerminalSymbol.RIGHT_BRACE &&
                c != TerminalSymbol.SPACE &&
                c != TerminalSymbol.ENTER &&
                c != TerminalSymbol.TABLE &&
                c != TerminalSymbol.NEWLINE) {
                
                buffer.Push(c);
            }

            string str = buffer.GetString();

            if (str == "true")
                return true;
            else if (str == "false")
                return false;
            else {
                throw new SyntaxError();
            }
        }

        #endregion

        #region ReadString

        /// <summary>
        /// Read a jsonstring object
        /// </summary>
        /// <returns></returns>
        public virtual IJsonObject Read2String() {
            //_internalReader.Back();
            char s = _internalReader.Next();

            CharBuffer buffer = new CharBuffer();

            if (s == TerminalSymbol.SINGLE_QUOTE || s == TerminalSymbol.DOUBLE_QUOTE) {
                if (!_internalReader.More()) {
                    throw new SyntaxError(string.Format("'{0}' is expected.", s));
                }

                char c = _internalReader.Next();
                char l = BufferedCharReader.EOF;

                while ((c != s || l == TerminalSymbol.BACKSLASH)) {
                    buffer.Push(c); 

                    l = c;

                    if (!_internalReader.More()) {
                        throw new SyntaxError(string.Format("'{0}' is expected.", s));
                    }

                    c = _internalReader.Next();
                }

                string str = string.Empty;
                c = (char)buffer.Pop();

                if (c != BufferedCharReader.EOF) {
                    buffer.Push(c);
                }

                str = buffer.GetString();
                return new JsonString(str);
            } else {
                throw new SyntaxError();
            }
        }

        #endregion

        #region ReadDateTime

        /// <summary>
        /// Read a JsonDateTime object
        /// </summary>
        /// <returns></returns>
        public virtual DateTime ReadDateTime() {
            CharBuffer buffer = new CharBuffer();

            char c = _internalReader.Next();

            if (c == TerminalSymbol.LEFT_ANGLE_BRACKET) {
                while ((c = _internalReader.Next()) != TerminalSymbol.RIGHT_ANGLE_BRACKET) {

                    buffer.Push(c);

                    if (!_internalReader.More()) {
                        break;
                    }
                }
            } else {
                throw new SyntaxError();
            }

            string str = buffer.GetString();

            DateTime dt;

            if (DateTime.TryParse(str, out dt)) {
                return dt;
            } else {
                throw new SyntaxError("Not datetime type.", _internalReader.GetString(100));
            }
        }

        #endregion

        #region ReadArray

        /// <summary>
        /// Read a JsonArray object 
        /// </summary>
        /// <returns></returns>
        public virtual JsonArray ReadArray() {
            char c = _internalReader.Next();

            if (c == TerminalSymbol.LEFT_SQUARE_BRACKET) {
                SkipSpace();

                JsonArray array = new JsonArray();

                c = _internalReader.Next();

                while (_internalReader.More() && c != TerminalSymbol.RIGHT_SQUARE_BRACKET) {
                    _internalReader.Back();
                    switch (c) {
                        // '[' mean Array Array.
                        case TerminalSymbol.LEFT_SQUARE_BRACKET:
                            array.Add(ReadArray());
                            break;
                        // '{' mean Dict Array
                        case TerminalSymbol.LEFT_BRACE:
                            array.Add(ReadDictionay());
                            break;
                        // ' or " mean String Array
                        case TerminalSymbol.SINGLE_QUOTE:
                        case TerminalSymbol.DOUBLE_QUOTE:
                            array.Add(new JsonString(ReadString()));
                            break;
                        case TerminalSymbol.LEFT_ANGLE_BRACKET:
                            array.Add(new JsonDateTime(ReadDateTime()));
                            break;
                        // t or f mean Boolean Array
                        case 't':
                        case 'f':
                            array.Add(new JsonBoolean((ReadBoolean())));
                            break;
                        // n mean null
                        case 'n':
                            array.Add(ReadNull());
                            break;
                        case TerminalSymbol.POSITIVE:
                        case TerminalSymbol.NEGATIVE:
                        case '0':
                        case '1':
                        case '2':
                        case '3':
                        case '4':
                        case '5':
                        case '6':
                        case '7':
                        case '8':
                        case '9':
                            array.Add(new JsonNumber(ReadNumber()));
                            break;
                        // other is unkown
                        default:
                            // Unkown token c
                            throw new SyntaxError();
                    }

                    SkipSpace();

                    c = _internalReader.Next();
                    
                    if (c == TerminalSymbol.COMMA) {
                        SkipSpace();
                        c = _internalReader.Next();
                    }
                }

                return array;
            } else {
                throw new SyntaxError();
            }
        }

        /// <summary>
        /// Read a jsonstring object
        /// </summary>
        /// <returns></returns>
        public virtual string ReadString() {
            //_internalReader.Back();
            char s = _internalReader.Next();

            CharBuffer buffer = new CharBuffer();

            if (s == TerminalSymbol.SINGLE_QUOTE || s == TerminalSymbol.DOUBLE_QUOTE) {
                if (!_internalReader.More()) {
                    throw new SyntaxError(string.Format("'{0}' is expected.", s));
                }

                char c = _internalReader.Next();
                char l = BufferedCharReader.EOF;

                while ((c != s || l == TerminalSymbol.BACKSLASH)) {
                    buffer.Push(c);

                    l = c;

                    if (!_internalReader.More()) {
                        throw new SyntaxError(string.Format("'{0}' is expected.", s));
                    }

                    c = _internalReader.Next();
                }

                string str = string.Empty;
                c = (char)buffer.Pop();

                if (c != BufferedCharReader.EOF) {
                    buffer.Push(c);
                }

                return buffer.GetString();
            } else {
                throw new SyntaxError();
            }
        }

        #endregion

        #region ReadDictionay

        /// <summary>
        /// Read a dictionary.
        /// </summary>
        /// <returns></returns>
        public virtual JsonDictionary ReadDictionay() {
             char c = _internalReader.Next();

             if (c == TerminalSymbol.LEFT_BRACE) {
                 JsonDictionary dict = new JsonDictionary();

                 SkipSpace();

                 c = _internalReader.Next();
                 
                 while (_internalReader.More() && c != TerminalSymbol.RIGHT_BRACE) {
                     _internalReader.Back();

                     KeyValuePair<string, IJsonObject> kvp = ReadKeyValue();

                     SkipSpace();

                     if (string.IsNullOrEmpty(kvp.Key)) {
                         throw new SyntaxError("Key must not be null or empty.", _internalReader.GetString(10));
                     }

                     dict[kvp.Key] = kvp.Value;

                     c = _internalReader.Next();

                     if (c == TerminalSymbol.COMMA) {
                         SkipSpace();
                         c = _internalReader.Next();
                     } 
                 }

                 return dict;
             } else {
                 throw new SyntaxError();
             }
        }

        #endregion

        #region SkipSpace

        /// <summary>
        /// Skip spaces that contains ' ', '\t', '\r' and '\n'.
        /// </summary>
        private void SkipSpace() {
            char c;
            while (_internalReader.More()) {
                c = _internalReader.Next();
               
                if (c == TerminalSymbol.SPACE || 
                    c == TerminalSymbol.TABLE || 
                    c == TerminalSymbol.ENTER || 
                    c == TerminalSymbol.NEWLINE)
                    continue;
                else {
                    _internalReader.Back();
                    return;
                }
            }
        }

        #endregion

        #region ReadNull

        /// <summary>
        /// Read a JsonNull object
        /// </summary>
        /// <returns></returns>
        public virtual JsonNull ReadNull() {
            CharBuffer buffer = new CharBuffer();
            char c;

            while (_internalReader.More()){
                c = _internalReader.Next();

                if (c != TerminalSymbol.COMMA &&
                    //c != TerminalSymbol.RIGHT_PARENTHESIS &&
                    c != TerminalSymbol.RIGHT_SQUARE_BRACKET &&
                    c != TerminalSymbol.RIGHT_BRACE &&
                    c != TerminalSymbol.SINGLE_QUOTE &&
                    c != TerminalSymbol.DOUBLE_QUOTE &&
                    c != TerminalSymbol.SPACE &&
                    c != TerminalSymbol.ENTER &&
                    c != TerminalSymbol.NEWLINE &&
                    c != TerminalSymbol.TABLE) {
                    buffer.Push(c);
                } else {
                    break;
                }
            }

            string str = buffer.GetString();
            if (str != "null"){
                throw new SyntaxError();
            }

            return JsonObject.Null;
        }

        #endregion

        #region ReadObject

        /// <summary>
        /// Read a json object
        /// </summary>
        /// <returns></returns>
        public IJsonObject ReadObject() {
            SkipSpace();

            if (_internalReader.More()) {
                char c = _internalReader.Next();
                _internalReader.Back();

                switch (c) {
                    //case TerminalSymbol.LEFT_PARENTHESIS:
                    //    return ReadTuple();
                    case TerminalSymbol.LEFT_BRACE:
                        return ReadDictionay();
                    case TerminalSymbol.LEFT_SQUARE_BRACKET:
                        return ReadArray();
                    case TerminalSymbol.LEFT_ANGLE_BRACKET:
                        return new JsonDateTime(ReadDateTime());
                    case TerminalSymbol.SINGLE_QUOTE:
                    case TerminalSymbol.DOUBLE_QUOTE:
                        return new JsonString(ReadString());
                    case 'n':
                        return ReadNull();
                    case 't':
                    case 'f':
                        return new JsonBoolean(ReadBoolean());
                    case TerminalSymbol.POSITIVE:
                    case TerminalSymbol.NEGATIVE:
                    case '0':
                    case '1':
                    case '2':
                    case '3':
                    case '4':
                    case '5':
                    case '6':
                    case '7':
                    case '8':
                    case '9':
                        return new JsonNumber(ReadNumber());
                    default:
                        throw new SyntaxError();
                }
            } else {
                return new JsonString("");
            }
        }

        #endregion

        #region ReadKeyValue

        /// <summary>
        /// Read a key value pair.
        /// </summary>
        /// <returns></returns>
        private KeyValuePair<string, IJsonObject> ReadKeyValue() {
            string key;
            IJsonObject obj;

            CharBuffer buffer = new CharBuffer();
            char c = _internalReader.Next();

            if (c == TerminalSymbol.RIGHT_BRACE) {
                return new KeyValuePair<string, IJsonObject>();
            }

            char t;
            // Begin with ' or "
            if (c == TerminalSymbol.SINGLE_QUOTE || c == TerminalSymbol.DOUBLE_QUOTE) {
                t = c;
                c = _internalReader.Next();
            } else {
                t = TerminalSymbol.COLON;
            }

            while (c != t) {
                if (c == TerminalSymbol.SPACE || 
                    c == TerminalSymbol.TABLE || 
                    c == TerminalSymbol.ENTER || 
                    c == TerminalSymbol.NEWLINE){
                    if (t != TerminalSymbol.COLON) {
                        throw new SyntaxError(@"Key can't contain ' ', '\r', '\n' or '\t'", _internalReader.GetString(10));
                    } else {
                        SkipSpace();
                        break;
                    }
                }

                if (char.IsLetter(c) || char.IsNumber(c) || c == '_') {
                    buffer.Push(c);
                } else {
                    // Key必须是字母、数字或_
                    throw new SyntaxError();
                }

                if (!_internalReader.More())
                    break;

                c = _internalReader.Next();
            }

            key = buffer.GetString();

            if (c != TerminalSymbol.COLON) {
                SkipSpace();
                _internalReader.Next();
            }

            SkipSpace();

            c = _internalReader.Next();
            _internalReader.Back();

            switch (c) {
                //case TerminalSymbol.LEFT_PARENTHESIS:
                //    obj = ReadTuple();
                //    break;
                // '[' mean Array Array.
                case TerminalSymbol.LEFT_SQUARE_BRACKET:
                    obj = ReadArray();
                    break;
                // '{' mean Dict Array
                case TerminalSymbol.LEFT_BRACE:
                    obj = ReadDictionay();
                    break;
                // ' or " mean String Array
                case TerminalSymbol.SINGLE_QUOTE:
                case TerminalSymbol.DOUBLE_QUOTE:
                    obj = new JsonString(ReadString());
                    break;
                case TerminalSymbol.LEFT_ANGLE_BRACKET:
                    obj = new JsonDateTime(ReadDateTime());
                    break;
                // t or f mean Boolean Array
                case 't':
                case 'f':
                    obj = new JsonBoolean(ReadBoolean());
                    break;
                // n mean null
                case 'n':
                    obj = ReadNull();
                    break;
                case TerminalSymbol.POSITIVE:
                case TerminalSymbol.NEGATIVE:
                case '0':
                case '1':
                case '2':
                case '3':
                case '4':
                case '5':
                case '6':
                case '7':
                case '8':
                case '9':
                    obj = new JsonNumber(ReadNumber());
                    break;
                // other is unkown
                default:
                    // Unkown token c
                    throw new SyntaxError();
            }

            return new KeyValuePair<string, IJsonObject>(key, obj);
        }

        #endregion

        #region IDispose

        private bool _displosed = false;

        public virtual void Close() {
            Dispose(true);
        }

        private void Dispose(bool disposing) {
            if (!_displosed) {
                if (disposing) {
                    Dispose(true);
                    _internalReader.Dispose();

                    GC.SuppressFinalize(this);
                }
                _displosed = true;
            }
        }

        public void Dispose() {
            Close();
        }

        #endregion
    }
}